# -*- coding: utf-8 -*-
#    Gedit OpenMSX plugin
#    Copyright (C) 2006  Rafael Jannone <rafael AT jannone DOT org>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import datetime
import array
import os

class ByteWriter:
	def __init__(self, dest):
		self.dest = dest

	def writeStr(self, txt, trunc):
		txt = txt.ljust(trunc)[:trunc]
		tmp = array.array('B')
		tmp.fromstring(txt)
		for value in tmp:
			self.dest.append(value & 255)

	def write8(self, value):
		self.dest.append(value & 255)

	def write16(self, value):
		self.dest.append(value & 255)
		self.dest.append((value >> 8) & 255)

	def write32(self, value):
		self.dest.append(value & 255)
		self.dest.append((value >> 8) & 255)
		self.dest.append((value >> 16) & 255)
		self.dest.append((value >> 24) & 255)

class RepeatIter:
	def __init__(self, value, times):
		self.value = value
		self.times = times

	def __iter__(self):
		return self

	def next(self):
		if (self.times <= 0):
			raise StopIteration
		self.times = self.times - 1
		return self.value

class FAT:
	def __init__(self, size):
		self.size = size
		self.data = array.array('B')
		self.data.extend(RepeatIter(0, size))
		self.cursor = 0;

	def writeStr(self, txt, trunc):
		txt = txt.ljust(trunc)[:trunc]
		tmp = array.array('B')
		tmp.fromstring(txt)
		for value in tmp:
			self.data[self.cursor] = value & 255
			self.cursor = self.cursor + 1			

	def write8(self, value):
		self.data[self.cursor] = value & 255
		self.cursor = self.cursor + 1

	def write16(self, value):
		self.data[self.cursor] = value & 255
		self.data[self.cursor + 1] = (value >> 8) & 255
		self.cursor = self.cursor + 2

	def write32(self, value):
		self.data[self.cursor] = value & 255
		self.data[self.cursor + 1] = (value >> 8) & 255
		self.data[self.cursor + 2] = (value >> 16) & 255
		self.data[self.cursor + 3] = (value >> 24) & 255
		self.cursor = self.cursor + 4

	def setCluster(self, cluster, value):
		print 'cluster ' + str(cluster) + ' <- ' + str(value)
		addr = (cluster*3) >> 1
		if cluster & 1:
			self.data[addr] = ((self.data[addr] & 0x0F) + (value << 4)) & 255
			self.data[addr+1] = (value >> 4) & 255
		else:
			self.data[addr] = value & 255
			self.data[addr+1] = ((self.data[addr+1] & 0x0F) + ((value >> 8) & 0x0F)) & 255

	def getCluster(self, cluster):
		addr = (cluster*3) >> 1
		if cluster & 1:
			return (self.data[addr] >> 4) + (self.data[addr+1] << 4)
		return self.data[addr] + ((self.data[addr+1] & 0x0F) << 8)

	def seek(self, pos):
		self.cursor = pos

	def save(self, f):
		self.data.tofile(f)


class DirEntry:
	def __init__(self, path, saveas = None):
		self.path = path
		if saveas:
			fname = saveas
		else:
			fname = os.path.basename(path)
		idx = fname.find(".")
		if idx > -1:
			self.fname = fname[:idx].upper()
			self.ext = fname[idx+1:].upper()
		else:
			self.fname = fname.upper()
			self.ext = ''
		self.attrib = 0		#char
		self.reserv = ''	#10

		# date/time
		fstat = os.stat(path)
		dt = datetime.datetime.fromtimestamp(fstat.st_mtime)
		self.time = (dt.second >> 1) + (dt.minute << 5) + (dt.hour << 11)
		self.date = dt.day + ((dt.month) <<5) + ((dt.year - 1980) << 9)

	def createFromString(self, disk, text):
		fat = disk.fat

		curcl=2
		while (curcl <= disk.maxcl) and (fat.getCluster(curcl) != 0):
			curcl = curcl + 1
		self.first = curcl			

		size=0
		prevcl=0
		finish = False
		while not finish and curcl <= disk.maxcl:
			chunk = text[size:size+disk.CLUSLEN]
			buffer = array.array('B')
			buffer.fromstring(chunk)
			amount = len(buffer)
			if amount > 0:
				size = size + amount
				disk.disk.seek(disk.data_ofs + (curcl-2) * disk.CLUSLEN, 0)
				buffer.tofile(disk.disk)
				if prevcl > 0:
					fat.setCluster(prevcl, curcl)
				prevcl = curcl
				curcl = curcl + 1
				while (curcl <= disk.maxcl) and fat.getCluster(curcl):
					curcl = curcl + 1
			else:
				finish = True
		if size:
			fat.setCluster(prevcl, disk.EOFAT)
		
		self.size = size
		return curcl <= disk.maxcl

	def create(self, disk):
		fat = disk.fat
		
		# open path for reading
		fil = open(self.path, "rb")

		curcl=2
		while (curcl <= disk.maxcl) and (fat.getCluster(curcl) != 0):
			curcl = curcl + 1
		self.first = curcl			

		size=0
		prevcl=0
		finish = False
		while not finish and curcl <= disk.maxcl:
			try:
				buffer = array.array('B')
				buffer.fromfile(fil, disk.CLUSLEN)
			except EOFError:			
				finish = True
			amount = len(buffer)
			if amount > 0:
				size = size + amount
				disk.disk.seek(disk.data_ofs + (curcl-2) * disk.CLUSLEN, 0)
				buffer.tofile(disk.disk)
				if prevcl > 0:
					fat.setCluster(prevcl, curcl)
				prevcl = curcl
				curcl = curcl + 1
				while (curcl <= disk.maxcl) and fat.getCluster(curcl):
					curcl = curcl + 1
		if size:
			fat.setCluster(prevcl, disk.EOFAT)
		
		self.size = size
		fil.close()
		return curcl <= disk.maxcl

	def writeFAT(self, fat):
		fat.writeStr(self.fname, 8)
		fat.writeStr(self.ext, 3)
		fat.write8(self.attrib)
		fat.writeStr(self.reserv, 10)
		fat.write16(self.time)
		fat.write16(self.date)
		fat.write16(self.first)
		fat.write32(self.size)
		
	def writeTo(self, dest):
		writer = ByteWriter(dest)
		writer.writeStr(self.fname, 8)
		writer.writeStr(self.ext, 3)
		writer.write8(self.attrib)
		writer.writeStr(self.reserv, 10)
		writer.write16(self.time)
		writer.write16(self.date)
		writer.write16(self.first)
		writer.write32(self.size)	

class WrDSK:

	SECLEN = 512	# length of sector
	CLUSLEN = 1024	# length of cluster
	EOFAT = 0xFFF	# end-of-fat

	BOOT = [
		0xEB,0xFE,0x90,0x56,0x46,0x42,0x2D,0x31,0x39,0x38,0x39,0x00,0x02,0x02,0x01,0x00,
		0x02,0x70,0x00,0xA0,0x05,0xF9,0x03,0x00,0x09,0x00,0x02,0x00,0x00,0x00,0xD0,0xED,
		0x53,0x58,0xC0,0x32,0xC2,0xC0,0x36,0x55,0x23,0x36,0xC0,0x31,0x1F,0xF5,0x11,0x9D,
		0xC0,0x0E,0x0F,0xCD,0x7D,0xF3,0x3C,0x28,0x28,0x11,0x00,0x01,0x0E,0x1A,0xCD,0x7D,
		0xF3,0x21,0x01,0x00,0x22,0xAB,0xC0,0x21,0x00,0x3F,0x11,0x9D,0xC0,0x0E,0x27,0xCD,
		0x7D,0xF3,0xC3,0x00,0x01,0x57,0xC0,0xCD,0x00,0x00,0x79,0xE6,0xFE,0xFE,0x02,0x20,
		0x07,0x3A,0xC2,0xC0,0xA7,0xCA,0x22,0x40,0x11,0x77,0xC0,0x0E,0x09,0xCD,0x7D,0xF3,
		0x0E,0x07,0xCD,0x7D,0xF3,0x18,0xB4,0x42,0x6F,0x6F,0x74,0x20,0x65,0x72,0x72,0x6F,
		0x72,0x0D,0x0A,0x50,0x72,0x65,0x73,0x73,0x20,0x61,0x6E,0x79,0x20,0x6B,0x65,0x79,
		0x20,0x66,0x6F,0x72,0x20,0x72,0x65,0x74,0x72,0x79,0x0D,0x0A,0x24,0x00,0x4D,0x53,
		0x58,0x44,0x4F,0x53,0x20,0x20,0x53,0x59,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF3,0x2A,
		0x51,0xF3,0x11,0x00,0x01,0x19,0x01,0x00,0x01,0x11,0x00,0xC1,0xED,0xB0,0x3A,0xEE,
		0xC0,0x47,0x11,0xEF,0xC0,0x21,0x00,0x00,0xCD,0x51,0x52,0xF3,0x76,0xC9,0x18,0x64,
		0x3A,0xAF,0x80,0xF9,0xCA,0x6D,0x48,0xD3,0xA5,0x0C,0x8C,0x2F,0x9C,0xCB,0xE9,0x89,
		0xD2,0x00,0x32,0x26,0x40,0x94,0x61,0x19,0x20,0xE6,0x80,0x6D,0x8A,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 
	]

	def __init__(self, fname):
		disk = open(fname, "w+b")
	
		bootbytes = array.array('B')
		bootbytes.fromlist(self.BOOT)
		bootbytes.tofile(disk)

		fat=FAT(self.SECLEN*self.BOOT[0x16])
		self.fat = fat

		fat.setCluster(0, self.BOOT[0x15]+0xF00)
		fat.setCluster(1, 0xFFF)
		self.dir_ofs = (1+self.BOOT[0x10]*self.BOOT[0x16])*self.SECLEN
		self.data_ofs = self.dir_ofs + (self.BOOT[0x11]+(self.BOOT[0x12]<<8))*32 # entry = 32 bytes
		self.maxcl = ((self.BOOT[0x13]+(self.BOOT[0x14]<<8))*self.SECLEN - self.data_ofs) / self.CLUSLEN +1

		# if disk size = 512
		clusbuf = array.array('B')
		clusbuf.extend(RepeatIter(0,self.SECLEN))
		disk.seek(self.dir_ofs, 0)
		pos = self.dir_ofs
		while pos < self.data_ofs:
			clusbuf.tofile(disk)
			pos = pos + self.SECLEN
		disk.seek(self.data_ofs+(self.maxcl-1)*self.CLUSLEN-self.SECLEN,0)
		clusbuf.tofile(disk)
		#print 'disk size = ', disk.tell()
		disk.flush()
		self.disk = disk		

	def hasEntry(self, addr):
		self.disk.seek(addr, 0)
		print 'disk pos = ', self.disk.tell()
		c = ord(self.disk.read(1))
		return (c != 0xE5) and (c != 0)	

	def findEmptyDir(self):
		dirpointer = self.dir_ofs
		while self.hasEntry(dirpointer) and (dirpointer < self.data_ofs):
			dirpointer = dirpointer + 32 # DirEntry
		if dirpointer < self.data_ofs:
			return dirpointer
		return None
		
	def writeEntry(self, entry, dirpointer):
		self.disk.seek(dirpointer, 0)
		buffer = array.array('B')
		entry.writeTo(buffer)
		buffer.tofile(self.disk)	
		
		'''					
		print dirpointer
		while self.hasEntry(dirpointer) and (dirpointer < self.data_ofs):
			dirpointer = dirpointer + 32 # DirEntry
		if dirpointer < self.data_ofs:
			entry = DirEntry(fname, saveas)
			if entry.ext in ["BAS", "ASM", "S  ", "C  ", "PAS", "TXT", "IDZ"]:
				txt = open(fname, "r").read()
				txt = txt.replace('\r', '').replace('\n', '\r\n')				
				entry.createFromString(self, txt)
			else:
				entry.create(self)
			self.disk.seek(dirpointer, 0)
			buffer = array.array('B')
			entry.writeTo(buffer)
			buffer.tofile(self.disk)
		else:
			return False
		return True
		'''

	def close(self):
		self.disk.seek(self.SECLEN, 0)
		slicsz = self.SECLEN * self.BOOT[0x16]
		slic = self.fat.data[:slicsz]
		for i in range(0,self.BOOT[0x10]):
			slic.tofile(self.disk)
		self.disk.close()
